package com.future.spring.context;

import com.future.spring.annotation.*;
import com.future.spring.exception.BeanCreationException;
import com.future.spring.exception.BeanDefinitionException;
import com.future.spring.exception.NoSuchBeanDefinitionException;
import com.future.spring.exception.UnsatisfiedDependencyException;
import com.future.spring.io.PropertyResolver;
import com.future.spring.io.ResourceResolver;
import com.future.spring.utils.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

/**
 * @author weber
 * @Description:
 * @Date 2025/7/28 21:48
 */
public class AnnotationConfigApplicationContext {

    private static final Logger logger = LoggerFactory.getLogger(AnnotationConfigApplicationContext.class);

    private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    PropertyResolver propertyResolver;

    protected Map<String, BeanDefinition> beans;

    private Set<String> creatingBeanNames;

    public AnnotationConfigApplicationContext(Class<?> configClass, PropertyResolver propertyResolver) {
        this.propertyResolver = propertyResolver;
        // 扫描获取所有bean的class类型
        final Set<String> beanClassNames = scanForClassNames(configClass);
        // 创建Bean定义
        this.beans = createBeanDefinitions(beanClassNames);
        /**
         * 创建BeanName检测循环依赖
         */
        this.creatingBeanNames = new HashSet<>();
        // 创建@Configuration类型的Bean
        this.beans.values().stream()
                .filter(this::isConfigurationDefinition).sorted()
                .map(def -> {
                    createBeanAsEarlySingleton(def);
                    return def.getName();
                }).toList();
        // 创建BeanPostProcessor类型的bean
        List<BeanPostProcessor> processors = this.beans.values().stream()
                .filter(this::isBeanPostProcessorDefinition).sorted()
                .map(def -> (BeanPostProcessor) createBeanAsEarlySingleton(def)).toList();
        this.beanPostProcessors.addAll(processors);
        // 创建其他普通Bean
        createNormalBeans();
        // 通过字段和set方法注入依赖
        this.beans.values().forEach(def -> {
            injectBean(def);
        });

        System.out.println();
    }

    private void injectBean(BeanDefinition def) {
        // 获取Bean实例，或被代理的原始实例
        final Object beanInstance = getProxiedInstance(def);
        try {
            injectProperties(def, def.getBeanClass(), beanInstance);
        } catch (ReflectiveOperationException e) {
            throw new BeanCreationException(e);
        }
    }

    private void injectProperties(BeanDefinition def, Class<?> clazz, Object bean) throws ReflectiveOperationException {
        // 在当前类查找Field和Method并注入
        for (Field field : clazz.getDeclaredFields()) {
            tryInjectProperties(def, clazz, bean, field);
        }
        for (Method method : clazz.getDeclaredMethods()) {
            tryInjectProperties(def, clazz, bean, method);
        }
        // 在父类查找Field和Method注入
        Class<?> superclass = clazz.getSuperclass();
        if (superclass != null) {
            injectProperties(def, superclass, bean);
        }
    }

    private void tryInjectProperties(BeanDefinition def, Class<?> clazz, Object bean, AccessibleObject acc) throws ReflectiveOperationException {
        Value value = acc.getAnnotation(Value.class);
        Autowired autowired = acc.getAnnotation(Autowired.class);
        if (value == null && autowired == null) {
            return;
        }
        Field field = null;
        Method method = null;
        if (acc instanceof Field f) {
            checkFieldOrMethod(f);
            f.setAccessible(true);
            field = f;
        }
        if (acc instanceof Method m) {
            checkFieldOrMethod(m);
            if (m.getParameters().length != 1) {
                throw new BeanCreationException(String.format("Cannot inject a non-setter method: %s for bean '%s': %s", m.getName(), def.getName(), def.getBeanClass().getName()));
            }
            m.setAccessible(true);
            method = m;
        }
        String accessibleName = field != null ? field.getName() : method.getName();
        Class<?> accessibleType = field != null ? field.getType() : method.getParameterTypes()[0];
        if (value != null && autowired != null) {
            throw new BeanCreationException(String.format("Cannot specify both @Autowired and @Value when inject %s.%s for bean '%s': %s", clazz.getName(), accessibleName, def.getName(), def.getBeanClass().getName()));
        }
        // @Value注入
        if (value != null) {
            Object propValue = this.propertyResolver.getRequireProperty(value.value(), accessibleType);
            if (field != null) {
                logger.atDebug().log("Field injection: {}:{}={}", def.getBeanClass().getName(), accessibleName, propValue);
                field.set(bean, propValue);
            }
            if (method != null) {
                logger.atDebug().log("Method injection: {}:{}={}", def.getBeanClass().getName(), accessibleName, propValue);
                method.invoke(bean, propValue);
            }
        }
        // @Autowired注入
        if (autowired != null) {
            String name = autowired.name();
            boolean required = autowired.value();
            Object depends = name.isEmpty() ? findBean(accessibleType) : findBean(name, accessibleType);
            if (required && depends == null) {
                throw new UnsatisfiedDependencyException(String.format("Dependency bean not found when inject %s.%s for bean '%s':%s", clazz.getSimpleName(), accessibleName, def.getName(), def.getBeanClass().getName()));
            }
            if (depends != null) {
                if (field != null) {
                    logger.atDebug().log(String.format("Field injection: {}.{}={}", def.getBeanClass().getName(), accessibleName, depends));
                    field.set(bean, depends);
                }
                if (method != null) {
                    logger.atDebug().log(String.format("Method injection: {}.{}={}", def.getBeanClass().getName(), accessibleName, depends));
                    method.invoke(bean, depends);
                }
            }
        }

    }

    private <T> T findBean(String name, Class<?> requiredType) {
        BeanDefinition def = findBeanDefinition(name, requiredType);
        if (def == null) {
            return null;
        }
        return (T) def.getRequiredInstance();
    }

    private <T> T findBean(Class<?> requiredType) {
        BeanDefinition beanDefinition = findBeanDefinition(requiredType);
        if (beanDefinition == null) {
            return null;
        }
        return (T) beanDefinition.getRequiredInstance();
    }

    private void checkFieldOrMethod(Member member) {
        int mod = member.getModifiers();
        if (Modifier.isStatic(mod)) {
            throw new BeanDefinitionException("Cannot inject static field ");
        }
        if (Modifier.isFinal(mod)) {
            if (member instanceof Field) {
                throw new BeanDefinitionException("Cannot inject final field ");
            }
            if (member instanceof Method) {
                logger.warn("Inject final method should be careful because it is not called on target bean when bean is proxied and may cause NullPointerException");
            }
        }
    }

    private Object getProxiedInstance(BeanDefinition def) {
        Object beanInstance = def.getInstance();
        // 如果Proxy改变了原始Bean,又希望注入到原始Bean,则由BeanPostProcessor指定原始Bean
        List<BeanPostProcessor> reversedBeanPostProcessors = new ArrayList<>(this.beanPostProcessors);
        Collections.reverse(reversedBeanPostProcessors);
        for (BeanPostProcessor beanPostProcessor : reversedBeanPostProcessors) {
            Object restoredInstance = beanPostProcessor.postProcessOnSetProperty(beanInstance, def.getName());
            if (restoredInstance != beanInstance) {
                logger.atDebug().log("BeanPostProcessor {} specified injected from {} to {}.", beanPostProcessor.getClass().getSimpleName(), def.getName(), restoredInstance);
                beanInstance = restoredInstance;
            }
        }
        return beanInstance;
    }

    private void createNormalBeans() {
        // 获取BeanDefinition列表
        List<BeanDefinition> defs = this.beans.values().stream()
                .filter(def -> def.getInstance() == null).sorted().toList();
        defs.forEach(def -> {
            if (def.getInstance() == null) {
                // 创建Bean
                createBeanAsEarlySingleton(def);
            }
        });

    }

    private Object createBeanAsEarlySingleton(BeanDefinition def) {

        logger.atDebug().log("Creating bean '%s' as early singleton: {}", def.getName(), def.getBeanClass().getName());
        if (!this.creatingBeanNames.add(def.getName())) {
            throw new UnsatisfiedDependencyException(String.format("Circular dependency detected when create bean %s", def.getName()));
        }
        // 创建方法：构造方法或者工厂方法
        Executable createFn = null;
        if (def.getFactoryName() == null) {
            // by constructor
            createFn = def.getConstructor();
        } else {
            // by factory method
            createFn = def.getConstructor();
        }
        // 创建参数
        final Parameter[] parameters = createFn.getParameters();
        final Annotation[][] parameterAnnotations = createFn.getParameterAnnotations();
        Object[] args = new Object[parameterAnnotations.length];
        for (int i = 0; i < parameters.length; i++) {
            final Parameter param = parameters[i];
            final Annotation[] paramAnnos = parameterAnnotations[i];
            final Value value = ClassUtils.getAnnotation(paramAnnos, Value.class);

            Autowired autowired = ClassUtils.getAnnotation(paramAnnos, Autowired.class);

            final boolean isConfiguration = isConfigurationDefinition(def);
            if (isConfiguration && autowired != null) {
                throw new BeanDefinitionException(String.format("Cannot specify @Autowired when create bean '%s':%s ", def.getName(), def.getBeanClass().getName()));
            }

            final boolean isBeanPostProcessor = isBeanPostProcessorDefinition(def);

            // 参数需要@Value或@Autowired两者之一:
            if (value != null && autowired != null) {
                throw new BeanDefinitionException(String.format("Cannot specify both @Autowired and @Value when create bean '%s':%s ", def.getName(), def.getBeanClass().getName()));
            }
            if (value == null && autowired == null) {
                throw new BeanDefinitionException(String.format("Must specify both @Autowired or @Value when create bean '%s':%s ", def.getName(), def.getBeanClass().getName()));
            }
            // 参数类型
            Class<?> type = param.getType();
            if (value != null) {
                args[i] = this.propertyResolver.getRequireProperty(value.value(), type);
            } else {
                // 参数是@Autowired
                String name = autowired.name();
                boolean required = autowired.value();
                // 依赖的BeanDefinition
                BeanDefinition dependsOnDef = name.isEmpty() ? findBeanDefinition(type) : findBeanDefinition(name, type);
                if (required && dependsOnDef == null) {
                    throw new BeanCreationException(String.format("Missing autowired bean definition when create bean '%s'", def.getName()));
                }
                if (dependsOnDef != null) {
                    // 获取依赖的Bean
                    Object autowiredBeanInstance = dependsOnDef.getInstance();
                    if (autowiredBeanInstance == null && !isConfiguration && !isBeanPostProcessor) {
                        // 当前依赖Bean尚未初始化，递归调用初始化该依赖
                        autowiredBeanInstance = createBeanAsEarlySingleton(dependsOnDef);
                    }
                    args[i] = autowiredBeanInstance;
                } else {
                    args[i] = null;
                }
            }

        }
        // 创建Bean实例
        Object instance = null;
        if (def.getFactoryName() == null) {
            try {
                instance = def.getConstructor().newInstance(args);
            } catch (Exception e) {
                throw new BeanDefinitionException(String.format("Cannot create bean '%s' : %s", def.getName(), def.getBeanClass().getName()), e);
            }
        } else {
            // 用@Bean方法创建
            Object configInstance = getBean(def.getFactoryName());
            try {
                instance = def.getFactoryMethod().invoke(configInstance, args);
            } catch (Exception e) {
                throw new BeanDefinitionException(String.format("Cannot create bean '%s' : %s", def.getName(), def.getBeanClass().getName()), e);
            }
        }
        def.setInstance(instance);
        // 调用BeanPostProcessor处理Bean
        for (BeanPostProcessor processor : this.beanPostProcessors) {
            Object processed = processor.postProcessBeforeInitialization(def.getInstance(), def.getName());
            if (processed == null) {
                throw new BeanDefinitionException(String.format("PostBeanProcessor return null when process bean '%s' : %s", def.getName(), def.getBeanClass().getName()));
            }
            // 如果一个BeanPostProcessor替换了原始Bean,则更新Bean引用
            if (def.getInstance() != processor) {
                logger.atDebug().log("Bean '{}' was replaced by post processor {}", def.getName(), processor.getClass().getName());
                def.setInstance(processed);
            }
        }
        return def.getInstance();
    }

    /**
     * 根据Type查找某个BeanDefinition，如果不存在返回null，如果存在多个返回@Primary标注的一个，
     * 如果有多个@Primary标注，或没有@Primary标注但找到多个，均抛出NoUniqueBeanDefinitionException
     *
     * @param type
     * @return
     */
    private BeanDefinition findBeanDefinition(Class<?> type) {
        List<BeanDefinition> defs = findBeanDefinitions(type);
        if (defs.isEmpty()) {
            return null;
        }
        if (defs.size() == 1) {
            return defs.get(0);
        }
        // 有多个，看@Primary
        List<BeanDefinition> primaryDefs = defs.stream().filter(BeanDefinition::isPrimary).toList();
        if (primaryDefs.size() == 1) {
            return primaryDefs.get(0);
        }
        if (primaryDefs.isEmpty()) {
            throw new BeanDefinitionException(String.format("No primary bean definition found for type %s", type.getName()));
        } else {
            throw new BeanDefinitionException(String.format("Multiple primary bean definitions found for type %s", type.getName()));
        }
    }

    /**
     * 根据Name和Type查找BeanDefinition，如果Name不存在，返回null，如果Name存在，但Type不匹配，抛出异常。
     *
     * @param name
     * @param requiredType
     * @return
     */
    private BeanDefinition findBeanDefinition(String name, Class<?> requiredType) {
        BeanDefinition def = findBeanDefinition(name);
        if (def == null) {
            return null;
        }
        if (!requiredType.isAssignableFrom(def.getBeanClass())) {
            throw new BeanDefinitionException(String.format("Bean '%s' is not of required type %s", name, requiredType.getName()));
        }
        return def;
    }

    private BeanDefinition findBeanDefinition(String name) {
        return this.beans.get(name);
    }

    private List<BeanDefinition> findBeanDefinitions(Class<?> type) {
        return this.beans.values().stream().filter(def -> type.isAssignableFrom(def.getBeanClass()))
                .sorted().toList();
    }

    private <T> T getBean(String beanName) {
        BeanDefinition def = this.beans.get(beanName);
        if (def == null) {
            throw new BeanDefinitionException(String.format("Bean '%s' not found", beanName));
        }
        return (T) def.getInstance();
    }

    boolean isConfigurationDefinition(BeanDefinition def) {
        return ClassUtils.getAnnotation(def.getBeanClass(), Configuration.class) != null;
    }

    boolean isBeanPostProcessorDefinition(BeanDefinition def) {
        return BeanPostProcessor.class.isAssignableFrom(def.getBeanClass());
    }

    /**
     * 根据扫描的className创建BeanDefinition
     *
     * @param beanClassNames
     * @return
     */
    private Map<String, BeanDefinition> createBeanDefinitions(Set<String> beanClassNames) {
        Map<String, BeanDefinition> defs = new HashMap<>();
        for (String className : beanClassNames) {
            Class<?> clazz;
            try {
                clazz = Class.forName(className);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (clazz.isAnnotation() || clazz.isEnum() || clazz.isInterface() || clazz.isRecord()) {
                continue;
            }
            // 是否标注@Component
            Component annotation = ClassUtils.getAnnotation(clazz, Component.class);
            if (annotation != null) {
                logger.atDebug().log("Found @ComponentS");
                int mod = clazz.getModifiers(); // 类修饰符
                if (Modifier.isAbstract(mod) || Modifier.isPrivate(mod)) {
                    throw new RuntimeException(String.format("Component %s is abstract or private ", className));
                }
                String beanName = ClassUtils.getBeanName(clazz);

                var def = new BeanDefinition(beanName, clazz, getSuitableConstructor(clazz), getOrder(clazz), clazz.isAnnotationPresent(Primary.class),
                        null, null, ClassUtils.findAnnotationMethod(clazz, PostConstruct.class),
                        ClassUtils.findAnnotationMethod(clazz, PreDestroy.class));
                addBeanDefinitions(defs, def);
                logger.atDebug().log("Added component {}", def);
                Configuration configuration = ClassUtils.getAnnotation(clazz, Configuration.class);
                if (configuration != null) {
                    if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                        throw new BeanDefinitionException("@Configuration class' " + clazz.getName() + "' cannot be annotated with BeanPostProcessor");
                    }
                    scanFactoryMethods(beanName, clazz, defs);
                }
            }
        }
        return defs;
    }

    /**
     * 扫描有@Bean注解的
     *
     * @param factoryBeanName
     * @param clazz
     * @param defs
     */
    private void scanFactoryMethods(String factoryBeanName, Class<?> clazz, Map<String, BeanDefinition> defs) {
        for (Method method : clazz.getDeclaredMethods()) {
            Bean bean = method.getAnnotation(Bean.class);
            if (bean != null) {
                int mod = method.getModifiers();
                if (Modifier.isAbstract(mod)) {
                    throw new RuntimeException(String.format("@Bean methode %s.%s must not be abstract", method.getName(), method.getName()));
                }
                if (Modifier.isFinal(mod)) {
                    throw new RuntimeException(String.format("@Bean methode %s.%s must not be final", method.getName(), method.getName()));
                }
                if (Modifier.isPrivate(mod)) {
                    throw new RuntimeException(String.format("@Bean methode %s.%s must not be private", method.getName(), method.getName()));
                }
                Class<?> beanClass = method.getReturnType();
                if (beanClass.isPrimitive()) {
                    throw new RuntimeException(String.format("@Bean methode %s.%s must not return primitive type", method.getName(), method.getName()));
                }
                if (beanClass == void.class || beanClass == Void.class) {
                    throw new RuntimeException(String.format("@Bean methode %s.%s must not return void", method.getName(), method.getName()));
                }
                var def = new BeanDefinition(ClassUtils.getBeanName(method),
                        beanClass,
                        factoryBeanName,
                        method,
                        getOrder(method),
                        method.isAnnotationPresent(Primary.class),
                        bean.initMethod().isEmpty() ? null : bean.initMethod(),
                        bean.destroyMethod().isEmpty() ? null : bean.destroyMethod(),
                        null,
                        null);
                addBeanDefinitions(defs, def);
            }
        }
    }

    private void addBeanDefinitions(Map<String, BeanDefinition> defs, BeanDefinition def) {
        if (defs.put(def.getName(), def) != null) {
            throw new RuntimeException(String.format("Duplicate definition %s", def.getName()));
        }
    }

    private int getOrder(Class<?> clazz) {
        Order order = clazz.getAnnotation(Order.class);
        return order == null ? Integer.MAX_VALUE : order.value();
    }

    private int getOrder(Method clazz) {
        Order order = clazz.getAnnotation(Order.class);
        return order == null ? Integer.MAX_VALUE : order.value();
    }


    Constructor<?> getSuitableConstructor(Class<?> clazz) {
        Constructor<?>[] constructors = clazz.getConstructors();
        if (constructors.length == 0) {
            constructors = clazz.getDeclaredConstructors();
            if (constructors.length != 1) {
                throw new RuntimeException(String.format("more than one constructor for class %s", clazz.getName()));
            }
        }
        if (constructors.length != 1) {
            throw new RuntimeException(String.format("more than one public constructor for class %s", clazz.getName()));
        }
        return constructors[0];
    }

    private Set<String> scanForClassNames(Class<?> configClass) {
        Set<String> classNameSet = new HashSet<>();
        ComponentScan scan = ClassUtils.getAnnotation(configClass, ComponentScan.class);
        final String[] scanPackages = scan == null || scan.value().length == 0 ?
                new String[]{configClass.getPackage().getName()} : scan.value();
        for (String pkg : scanPackages) {
            // 扫描package
            var rr = new ResourceResolver(pkg);
            List<String> classList = rr.scan(res -> {
                String name = res.name();
                if (name.endsWith(".class")) {
                    return name.substring(0, name.length() - 6).replace('/', '.').replace('\\', '.');
                }
                return null;
            });
            classNameSet.addAll(classList);
        }
        return classNameSet;
    }

    public <T> T getBean(Class<T> requiredType) {
        BeanDefinition def = findBeanDefinition(requiredType);
        if (def == null) {
            throw new NoSuchBeanDefinitionException(String.format("No bean defined with type '%s'.", requiredType));
        }
        return (T) def.getRequiredInstance();
    }
}
